package MathTextModeller
{
	import MyDisplayObjects.DraggableImage;
	import MyDisplayObjects.DraggableMC;
	
	import TaskStructure.LogEvent;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Mouse;
	
	/** An object of this class displays all sentence display models side-by-side horizontally.  All objects within sentences are disabled except objects and unknowns. */
	public class SentenceDisplayPanel extends MovieClip
	{
		public var currentSentenceDisplay:SentenceDisplay;
		
		// private variables
		private var WIDTH:Number, HEIGHT:Number;
		private var sentenceDisplays:Array;
		private var unitMCs:Array = null; // the unit objects (e.g., marbles)
		private var variableMCs:Array = null; // the variable
		private var variableUnitMCs:Array = null; // unit objecs placed on the variable
		private var extra_unitMC:DraggableMC;
		private var dragging_unitMC:DraggableMC;
		private var draggedfrom_variableMC:DraggableMC;
		// private accessible by getters
		private var _scale:Number;
		// getters and setters
		public function get scale():Number{return _scale;}
		public function get numUnits():uint{if (unitMCs != null){return unitMCs.length} else {return 0;}}
		public function get numVariables():uint{if (variableMCs != null){return variableMCs.length} else {return 0;}}
		
		/** Models each sentence in array.  Will fit all sentences within the given width and height without changing sentence display proportions. */
		public function SentenceDisplayPanel(sentenceDisplays:Array, width:Number, height:Number)
		{
			this.sentenceDisplays = sentenceDisplays;
			this.WIDTH = width; this.HEIGHT = height;
			//this.graphics.drawRect(0, 0, width, height);
			
			unitMCs = new Array(); variableMCs = new Array(); variableUnitMCs = new Array();
			// assumes that all displays will have the same height/width ratio
			if (sentenceDisplays.length > 0)
			{
				
				var sentenceRatio:Number = sentenceDisplays[0].width/sentenceDisplays[0].height;
				if ((WIDTH/sentenceDisplays.length)/HEIGHT> sentenceRatio)
				{ /// FILLING THE HEIGHT
					_scale = HEIGHT/sentenceDisplays[0].height/HEIGHT;
					WIDTH = sentenceRatio*HEIGHT*sentenceDisplays.length;
				} else
				{ /// FILLING THE WIDTH
					_scale = (WIDTH/sentenceDisplays.length)/sentenceDisplays[0].width;
					HEIGHT = (WIDTH/sentenceDisplays.length)/sentenceRatio;					
				}
				this.graphics.drawRect(0, 0, WIDTH, HEIGHT);
				
				// go through each sentence display, scale and add to display in divided section of this display object
				for (var i:uint=0; i < sentenceDisplays.length; i++)
				{
					var sentenceDisplay:SentenceDisplay = sentenceDisplays[i]; 	
					// center in display space
					sentenceDisplay.panelScale =_scale;
					sentenceDisplay.x = WIDTH/sentenceDisplays.length * i + (WIDTH/sentenceDisplays.length - sentenceDisplay.width)/2;
					//sentenceDisplay.y = (thisHeight - sentenceDisplay.height)/2;
					this.addChild(sentenceDisplay);
				}
				// go back through each and transition objects to Panel
				for (i=0; i < sentenceDisplays.length; i++)
				{
					sentenceDisplay = sentenceDisplays[i]; 	
					sentenceDisplay.transitionToPanelView(this);
				}
				
			} else
			{
				this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR));
			}
		}
		
		/** Unit objects (e.g., marbles) are transitioned from the display to this object */
		public function addUnitMC (dmc:DraggableMC, fromDisplay:DisplayObject):DraggableMC
		{			
			unitMCs.push(dmc);
			dmc.addEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
			// when adding from an external object, which should be a SentenceDisplay, scale object and position
			dmc.scaleX = this._scale;
			dmc.scaleY = this._scale;
			if (fromDisplay != this)
			{
				dmc.x *= this._scale;
				dmc.y *= this._scale;
				dmc.x += fromDisplay.x;
				dmc.y += fromDisplay.y;
			}
			dmc.origin = new Point(dmc.x, dmc.y);
			this.addChild(dmc);
			return dmc;
		}
		
		
		/** Variable object is transitioned from the display thiss to this object */
		public function addVariableMC (dmc:DraggableMC, childDisplay:DisplayObject):DraggableMC
		{
			variableMCs.push(dmc);
			dmc.addEventListener(MouseEvent.MOUSE_DOWN, handleVariableDraggingStarted);
			dmc.scaleX = this._scale;
			dmc.scaleY = this._scale;
			dmc.x *= this._scale;
			dmc.y *= this._scale;
			dmc.x += childDisplay.x;
			dmc.y += childDisplay.y;
			dmc.origin = new Point(dmc.x, dmc.y);
			this.addChild(dmc);
			dmc.disabled = true;
			return dmc;
		}
		
		/** Return all shopes to the original position */
		public function returnAllToOrigins():void
		{
			for (var i:uint=0; i < unitMCs.length; i++)
			{
				DraggableMC(unitMCs[i]).returnToOrigin();
			}
		}
		
		/** Removes everything */
		public function removeAll():void
		{
			var dmc:DraggableMC;
			for (var i:uint=0; i < unitMCs.length; i++)
			{
				dmc = DraggableMC(unitMCs[i]);
				if (dmc.hasEventListener(MouseEvent.MOUSE_DOWN)) dmc.removeEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
				if (dmc.parent == this) this.removeChild(dmc);
			}
			for (i=0; i < variableMCs.length; i++)
			{
				dmc = DraggableMC(variableMCs[i]);
				if (dmc.hasEventListener(MouseEvent.MOUSE_DOWN)) dmc.removeEventListener(MouseEvent.MOUSE_DOWN, handleVariableDraggingStarted);
				if (dmc.parent == this) this.removeChild(dmc);
			}
		}
		
		/** Removes both a unit and its origin point from arrays */
		public function removeUnitMC (dmc:DraggableMC):void
		{
			for (var i:uint=0; i < unitMCs.length; i++)
			{
				if (unitMCs[i] == dmc)
				{
					if (dmc.hasEventListener(MouseEvent.MOUSE_DOWN)) dmc.removeEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
					this.removeChild(dmc);
				}
			}
		}
		
		/** Removes both a unit and its origin point from arrays */
		public function removeVariableMC (dmc:DraggableMC):void
		{
			for (var i:uint=0; i < variableMCs.length; i++)
			{
				if (unitMCs[i] == dmc)
				{
					if (dmc.hasEventListener(MouseEvent.MOUSE_DOWN)) dmc.removeEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
					this.removeChild(dmc);
				}
			}
		}
		///////////////////////////////////// DRAGGING OBJECTS AROUND SCREEN - I.E., THE OBJECTIVE OF THIS OBJECT ///////////////////
		//////////////// POSSIBILITIES
		/////// 1) Starts at a unit object, ends at a different unit object
		/////// 2) Starts at a unit object, ends at a variable object
		/////// 3) Starts at a unit object, ends on neither
		/////// 4) Starts at a variable object, ends at a unit object
		/////// 5) Starts at a variable object, ends at a different variable object
		/////// 6) Starts at a variable object, ends on neither	
		/** When a user clicks directly on a variable create a new unitMC that will be dragged to be matched.  If successfully matched place at location near variable
		 */
		private function handleVariableDraggingStarted (event:MouseEvent):void
		{
			// create a new unitMC to drag, just duplicate the first one in the array
			if (unitMCs.length > 0) 
			{
				draggedfrom_variableMC = DraggableMC(event.currentTarget);
				dragging_unitMC = DraggableMC(unitMCs[0]).duplicate();
				//make sure that we have loaded the image first
				dragging_unitMC.addEventListener( Event.COMPLETE, _handleVariableDraggingStarted);
				
			}
		}
			private function _handleVariableDraggingStarted (event:Event):void
			{
				dragging_unitMC.removeEventListener( Event.COMPLETE, _handleVariableDraggingStarted);
				extra_unitMC = dragging_unitMC.duplicate(); 
				dragging_unitMC.x = this.mouseX - dragging_unitMC.width/2;
				dragging_unitMC.y = this.mouseY - dragging_unitMC.height/2;
				dragging_unitMC.mouseEnabled = true;
				this.addUnitMC(dragging_unitMC, this);
				dragging_unitMC.startDrag(false, new Rectangle(0, 0, dragging_unitMC.parent.width-dragging_unitMC.width, dragging_unitMC.parent.height-dragging_unitMC.height) );
				this.stage.addEventListener(MouseEvent.MOUSE_UP, handleDraggingComplete);
				Mouse.hide();
			}
		/** When user clicks directly on a unit mc start dragging */
		private function handleDraggingStarted (event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
			dragging_unitMC = DraggableMC(event.currentTarget);
			
			if (dragging_unitMC.matchedPair != null) unmatchUnits(dragging_unitMC, dragging_unitMC.matchedPair);
			// create an extra in case we need one for placing in the variable
			extra_unitMC = dragging_unitMC.duplicate(); 
			
			// swap this object to top to ensure that it will pass over everything else
			this.removeChild(dragging_unitMC);
			this.addChild(dragging_unitMC);
			
			this.stage.addEventListener(MouseEvent.MOUSE_UP, handleDraggingComplete);
		}
		/** After the user has dragged a unit object around the screen check to see if he or has successfully touched another unit mc or the variable mc.
		 * Return to original position after completed and tag both objects if successful 
		 */ 
		private function handleDraggingComplete (event:MouseEvent):void
		{
			this.stage.removeEventListener(MouseEvent.MOUSE_UP, handleDraggingComplete);
			
			var hit_unitMC:DraggableMC; 
			var hit_variableMC:DraggableMC; 
			
			// did we release on a unit object, did we release on a variable?
			// go through each object in the unitMCs list and see if these two objects are touching
			for each (var unitMC:DraggableMC in unitMCs)
			{
				if (unitMC != dragging_unitMC && dragging_unitMC.hitTestObject(unitMC))
				{
					hit_unitMC = unitMC; break; 
				}
			}
			// Check to see if we dragged to the variable MC
			for each (var variableMC:DraggableMC in variableMCs)
			{
				if (draggedfrom_variableMC != variableMC && dragging_unitMC.hitTestObject(variableMC))
				{
					hit_variableMC = variableMC; break;
				}
			}
			
			// did we end on a variable, a unit, or neither?
			if (hit_variableMC != null)
			{ // released on a variable object
				// did we start on a variable or a unit
				if (draggedfrom_variableMC != null)
				{ // --VARIABLE TO VARIABLE--
					dragging_unitMC.stopDrag();
					Mouse.show();
					matchVariableUnitToVariable(draggedfrom_variableMC, dragging_unitMC, hit_variableMC, extra_unitMC);
				} else
				{ // --UNIT TO VARIABLE--
					matchUnitToVariable(dragging_unitMC, hit_variableMC, extra_unitMC);
				}				
			} else if (hit_unitMC != null)
			{ // released on a unit object
				// did we start on a variable or a unit
				if (draggedfrom_variableMC != null)
				{ // --VARIABLE TO UNIT--
					dragging_unitMC.stopDrag();
					Mouse.show();
					matchVariableUnitToUnit(draggedfrom_variableMC, dragging_unitMC, hit_unitMC);
				} else
				{ // --UNIT TO UNIT--
					this.matchUnitToUnit(dragging_unitMC, hit_unitMC);
				}	
			} else
			{ // released on nothing
				// --VARIABLE TO NOTHING--
				if (draggedfrom_variableMC != null)
				{ // started from variable
					dragging_unitMC.stopDrag();
					Mouse.show();
					this.returnUnit(dragging_unitMC, draggedfrom_variableMC);
				} else
				{ // --UNIT TO NOTHING--
					this.returnUnit(dragging_unitMC, dragging_unitMC.firstAssociatedSibling);
				}	
			}
			dragging_unitMC.addEventListener(MouseEvent.MOUSE_DOWN, handleDraggingStarted);
			dragging_unitMC = null;
			draggedfrom_variableMC = null;
		}
		
		/////// SUBFUNCTIONS TO DEAL WITH MATCHING-UP OBJECTS
		
		/** Match two units, the first unit should already been unmatched, the second might still be matched */
		private function matchUnitToUnit (sourceMC:DraggableMC, targetMC:DraggableMC):void
		{
			if (targetMC.matchedPair != null)
			{ // matched
				// if there is a partner then there should be borders and we need to drop the pair stuff
				if (targetMC.matchedPair != null) unmatchUnits(targetMC, targetMC.matchedPair);
			}
			returnUnit(sourceMC);
			matchUnits(sourceMC, targetMC);	
			this.dispatchEvent(new LogEvent(LogEvent.OBJECTS_MATCHED, sourceMC.name, getSentenceDisplayNameUnderPoint(sourceMC.origin), sourceMC.origin.x, sourceMC.origin.y, targetMC.name, getSentenceDisplayNameUnderPoint(targetMC.origin), targetMC.origin.x, targetMC.origin.y));
		}
		/** Match a unitMC to a new unitMC created at the variableMC, targetMC MC is the new one created at the variable */
		private function matchUnitToVariable (sourceMC:DraggableMC, targetvariableMC:DraggableMC, targetMC:DraggableMC):void
		{
			// make sure we were given a new unit to match this one with
			if (targetMC != null)
			{
				targetvariableMC.addAssociatedSibling(targetMC);
				targetMC.addAssociatedSibling(targetvariableMC);
				targetMC.moveSquareFromPoint(new Point(targetvariableMC.x+targetvariableMC.width/2, targetvariableMC.y+targetvariableMC.height/2), targetvariableMC.numAssociatedSiblings, 1.25, scale);
				this.addUnitMC(targetMC, this);					
				returnUnit(sourceMC);
				matchUnits(sourceMC, targetMC);
				this.dispatchEvent(new LogEvent(LogEvent.OBJECTS_MATCHED, sourceMC.name, getSentenceDisplayNameUnderPoint(sourceMC.origin), sourceMC.origin.x, sourceMC.origin.y, targetvariableMC.name, getSentenceDisplayNameUnderPoint(new Point(targetvariableMC.x, targetvariableMC.y)), targetMC.origin.x, targetMC.origin.y));		
			} 
		}
		/** Match a unitMC that originated at the variable to a new target.  Place the source back at the variable */
		private function matchVariableUnitToUnit (sourcevariableMC:DraggableMC, sourceMC:DraggableMC, targetMC:DraggableMC):void
		{
			sourcevariableMC.addAssociatedSibling(sourceMC);
			sourceMC.addAssociatedSibling(sourcevariableMC);
			sourceMC.moveSquareFromPoint(new Point(sourcevariableMC.x+sourcevariableMC.width/2, sourcevariableMC.y+sourcevariableMC.height/2), sourcevariableMC.numAssociatedSiblings, 1.25, scale);
			if (targetMC.matchedPair != null) unmatchUnits(targetMC, targetMC.matchedPair);
			matchUnits(sourceMC, targetMC);	
			this.dispatchEvent(new LogEvent(LogEvent.OBJECTS_MATCHED, sourcevariableMC.name, getSentenceDisplayNameUnderPoint(new Point(sourcevariableMC.x, sourcevariableMC.y)), sourceMC.origin.x, sourceMC.origin.y, targetMC.name, getSentenceDisplayNameUnderPoint(targetMC.origin), targetMC.origin.x, targetMC.origin.y));
			
		}
		/** Go from a variable to a variable, not sure if this should be used, maybe for extensions */
		private function matchVariableUnitToVariable (sourcevariableMC:DraggableMC, sourceMC:DraggableMC, targetvariableMC:DraggableMC, targetMC:DraggableMC):void 
		{
			targetvariableMC.addAssociatedSibling(targetMC);
			targetMC.addAssociatedSibling(targetvariableMC);
			targetMC.moveSquareFromPoint(new Point(targetvariableMC.x+targetvariableMC.width/2, targetvariableMC.y+targetvariableMC.height/2), targetvariableMC.numAssociatedSiblings, 1.25, scale);
			returnUnit(sourceMC);
			sourcevariableMC.addAssociatedSibling(sourceMC);
			sourceMC.addAssociatedSibling(sourcevariableMC);
			sourceMC.moveSquareFromPoint(new Point(sourcevariableMC.x+sourcevariableMC.width/2, sourcevariableMC.y+sourcevariableMC.height/2), sourcevariableMC.numAssociatedSiblings, 1.25, scale);
			this.addUnitMC(targetMC, this);					
			matchUnits(sourceMC, targetMC);
			this.dispatchEvent(new LogEvent(LogEvent.OBJECTS_MATCHED, sourcevariableMC.name, getSentenceDisplayNameUnderPoint(new Point(sourcevariableMC.x, sourcevariableMC.y)), sourceMC.origin.x, sourceMC.origin.y, targetvariableMC.name, getSentenceDisplayNameUnderPoint(new Point(targetvariableMC.x, targetvariableMC.y)), targetMC.origin.x, targetMC.origin.y));
			
		}
		/** Return the unit to its origin or, if variableMC is not null, remove the unit completely */
		private function returnUnit (sourceMC:DraggableMC, variableMC:DraggableMC = null):void
		{
			if (variableMC != null)
			{ // this came from a variable, remove completely
				this.removeChild(sourceMC);
			} else
			{ // this came from a previous unit, return to original space
				// return dragging_unitMC to its original space
				sourceMC.returnToOrigin();				
			}
		}
		private function matchUnits (sourceMC:DraggableMC, targetMC:DraggableMC):void
		{
			// Figure out what color to assign to this tagged border
			var borderColor:uint;
			var sentenceDisplayIndexFrom:int, sentenceDisplayIndexTo:int; 
			sentenceDisplayIndexFrom = this.getSentenceDisplayIndexUnderPoint(sourceMC.origin);
			sentenceDisplayIndexTo = this.getSentenceDisplayIndexUnderPoint(targetMC.origin);
			if (sentenceDisplayIndexFrom == 0 && sentenceDisplayIndexTo == 1 || sentenceDisplayIndexFrom == 1 && sentenceDisplayIndexTo == 0)
			{
				borderColor = 0x0000FF;
			} else if (sentenceDisplayIndexFrom == 1 && sentenceDisplayIndexTo == 2 || sentenceDisplayIndexFrom == 2 && sentenceDisplayIndexTo == 1)
			{
				borderColor = 0xFF00FF;
			} else if (sentenceDisplayIndexFrom == 0 && sentenceDisplayIndexTo == 2 || sentenceDisplayIndexFrom == 2 && sentenceDisplayIndexTo == 0)
			{
				borderColor = 0xFF0000;
			}
			sourceMC.drawBorder(sourceMC.width/10, borderColor, 1);
			targetMC.drawBorder(sourceMC.width/10, borderColor, 1);
			sourceMC.matchedPair = targetMC;
			targetMC.matchedPair = sourceMC;
			updateNumberModelOfDraggableMC (sourceMC, 1);
			updateNumberModelOfDraggableMC (targetMC, 1);
			
		}
		/** Given two matched units removes the match.  If the target is on a variable, then remove completely. */
		private function unmatchUnits (sourceMC:DraggableMC, targetMC:DraggableMC):Boolean
		{
			if (sourceMC.matchedPair == targetMC)
			{
				this.dispatchEvent(new LogEvent(LogEvent.OBJECTS_UNMATCHED, sourceMC.name, getSentenceDisplayNameUnderPoint(sourceMC.origin), sourceMC.origin.x, sourceMC.origin.y, targetMC.name, getSentenceDisplayNameUnderPoint(targetMC.origin), targetMC.origin.x, targetMC.origin.y));
				sourceMC.drawBorder(0);
				targetMC.drawBorder(0);
				sourceMC.matchedPair = null;
				targetMC.matchedPair = null;
				// go through all variableMCs, look to see if either the target or source is associated, if so un-associate and remove completely
				for each (var variableMC:DraggableMC in variableMCs)
				{
					if (variableMC.indexOfAssociatedSibling(targetMC) > -1)
					{
						variableMC.removeAssociatedSibling(targetMC);
						this.removeChild(targetMC);
						break;
					}
				}
				updateNumberModelOfDraggableMC (sourceMC, -1);
				updateNumberModelOfDraggableMC (targetMC, -1);
				
				return true;
			} else
			{
			
				return false;
			}
		}
		/** Gives the index of the sentenceDisplay (stored in an array) under the given point. -1 means not on sentence display */
		private function getSentenceDisplayIndexUnderPoint(p:Point):int
		{
			for (var i:uint=0; i < sentenceDisplays.length; i++)
			{
				if (p.x >= sentenceDisplays[i].x && p.x <= sentenceDisplays[i].x+sentenceDisplays[i].width)
				{
					return i;
				}					
			}
			return -1;
		}
		/** Gives the index of the sentenceDisplay (stored in an array) under the given point. -1 means not on sentence display */
		private function getSentenceDisplayNameUnderPoint(p:Point):String
		{
			for (var i:uint=0; i < sentenceDisplays.length; i++)
			{
				if (p.x >= sentenceDisplays[i].x && p.x <= sentenceDisplays[i].x+sentenceDisplays[i].width)
				{
					if (i==0) { return "left";}
					else if (i==1) { return "middle";}
					else if (i==2) { return "right";}
				}					
			}
			return "";
		}
		
		////////////////////////////////////////////// WORK WITH NUMBERS //////////////////////////////////////////////////
		public function updateNumberModelOfDraggableMC(dmc:DraggableMC, dx:Number):void
		{
			var index:int = getSentenceDisplayIndexUnderPoint(new Point(dmc.x, dmc.y));
			if (index == 0)
			{
				ActionModeller(parent).numberModel.increaseLeft(dx);
			} else if (index == 1)
			{
				ActionModeller(parent).numberModel.increaseMiddle(dx);
			} else if (index == 2)
			{
				ActionModeller(parent).numberModel.increaseRight(dx);
			} 
		}
	}
}